package uk.ac.ebi.pride.tools.pap.db;

import org.apache.log4j.Logger;

import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContext;
import java.sql.Connection;
import java.sql.SQLException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

/**
 * Load-on-startup servlet to configure connection pools
 *
 * @author Richard Cote
 * @author Florian Reisinger
 * @since 0.1
 */

public class ConfiguratorListener implements ServletContextListener {

    private static final Logger logger = Logger.getLogger(ConfiguratorListener.class);

    public void contextInitialized(ServletContextEvent servletContextEvent) {
        logger.info("Logging activated for PRIDE Accession Provider Web App");

        ServletContext sctx = servletContextEvent.getServletContext();
        String connProvider = sctx.getInitParameter("connectionProviderClass");
        logger.info("Using class '" + connProvider + "' as connection provider.");

        // we want to make sure at this point that we actually have something before we go on
        if (connProvider == null || connProvider.length() < 2) {
            IllegalStateException e = new IllegalStateException("Specified connection provider class '" + connProvider
                    + "' is not valid! It must not be null and it must be longer that a single character.");
            logger.error("Configuration error.", e);
            throw e;
        }

        ConnectionProvider connectionProvider;
        try {
            connectionProvider = getConnectionProvider(connProvider);
        } catch (ConnectionConfigurationException e) {
            logger.error("Could not instanciate connection provider: " + connProvider, e);
            throw new IllegalStateException("Could not create a ConnectionProvider! No point in proceeding.", e);
        }

        Connection conn = null;

        try {
            // configure search code to use the correct connection provider
            ConnectionProviderFactory providerFactory = ConnectionProviderFactory.getInstance();
            providerFactory.setConnectionProvider(connectionProvider);

            // test to see that we have valid connections
            conn = connectionProvider.getConnection();
            conn.close();
            conn = null;

            logger.info("Database connections established");

        } catch (SQLException e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {/*no op*/}
            }
        }

    }

    private ConnectionProvider getConnectionProvider(String connProviderClass) throws ConnectionConfigurationException {
        // check correct usage of the method (parameter)
        assert(connProviderClass != null && connProviderClass.length() > 1) : "Wrong use of method! " +
                "The specified parameter connProviderClass must not be null or shorter than 2 characters.";

        ConnectionProvider prov;
        Class aClass;
        try {
            aClass = Class.forName(connProviderClass);
        } catch (ClassNotFoundException e) {
            throw new ConnectionConfigurationException("Could not find the specified connection provider class: " + connProviderClass, e);
        }
        logger.debug("Found class: " + connProviderClass);

        // now that we have the Class, try to create a object
        // Since the implementations are usually factories, we might have to invoke a static
        // getInstance method instead of getting a instance with class.newInstance()
        Object obj = null;
        boolean success;
        try {
            obj  = aClass.newInstance();
            success = true;
        } catch (Exception e) {
            success = false;
        }

        // if we did not succeed with the class.newInstance() method,
        // we try a getInstance method in case it is a factory
        if (!success) {
            logger.info("Initial attempt to instantiate connection provider was not successful, trying factory approach with 'getInstance' method." );
            try {
                Method m = aClass.getDeclaredMethod("getInstance");
                obj = m.invoke(null);
            } catch (NoSuchMethodException e) {
                throw new ConnectionConfigurationException("Could not find a 'getInstance' method. " +
                        "This class does not seem to be a factory either.", e);
            } catch (Exception e) {
                throw new ConnectionConfigurationException("Could not invoke the 'getInstance' method!", e);
            }
        }

        // now we should have a instance of the ConnectionProvider
        if (obj != null && obj instanceof ConnectionProvider ) {
            prov = (ConnectionProvider) obj;
        } else {
            IllegalStateException e = new IllegalStateException("Could not create a ConnectionProvider!");
            logger.error("Error instantiating connection provider.", e);
            throw e;
        }
        logger.info("Connection provider instantiated.");

        return prov;
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

}